

#include "Session.hpp"
#include "Host.hpp"
#include "Peer.hpp"
#include "../muduo/net/Buffer.h"
#include <string>
#include <algorithm>
#include <sstream>
#include <iterator>
#include <iostream>
#include <functional>

Session::Session(Host* ower,muduo::net::EventLoop * loop, muduo::net::TcpConnectionPtr& conn, std::shared_ptr<Peer>const & peer,
    std::shared_ptr<SessionConfig> sessionConfig,
    std::shared_ptr<spdlog::logger> logger):
    ower_(ower), 
    loop_(loop),
    conn_(conn), 
    sendMonitor_(std::make_shared<flowrate::Monitor>(0,0)),
    recvMonitor_(std::make_shared<flowrate::Monitor>(0,0)),
    config_(sessionConfig),
    peer_(peer),
    dropped_(false),
    connect_(muduo::Timestamp::now()),
    lastReceived_(connect_),
    logger_(logger)
{
    auto s_peer=peer_.lock();
    conn_->setMessageCallback(
        std::bind(&Session::onMessage,this, std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
    pingTimeout_ = loop_->runEvery(config_->pingInterval, [this](){
        this->ping();
    });
}
void Session::ping()
{
    logger_->debug("Send Ping to {}", conn_->peerAddress().toIpPort());
    std::string s;
    s+=PingPacket;
    conn_->send(s.data(),s.length());
    lastPing_=muduo::Timestamp::now();
    sendMonitor_->Update(s.length());

    logger_->debug("Starting pong timer dur {}s", config_->pongTimeout);
    pongTimeout_ = loop_->runAfter(config_->pongTimeout,[this](){
        logger_->debug("Pong timeout");
        this->disconnect(DisconnectReason(PingTimeout));
    });
}
Session::~Session()
{
    logger_->info("Closing peer session");
}


std::string Session::toString()
{
    char s[80];
    if(auto s_peer=peer_.lock())
        sprintf(s,"Session connected with %s",s_peer->address().toIpPort().c_str());
    return std::string(s);
}

void Session::disconnect(DisconnectReason const reason)
{
    logger_->info("Disconnect with peer {} for {}", conn_->peerAddress().toIpPort(), reasonOf(reason));

    if(isConnected())
    {
        conn_->shutdown();
    }
    dropped_=true;
}

void Session::send(unsigned char chID,const bytes& message)
{
    std::ostringstream oss;
    if (!message.empty())
    {
        std::copy(message.begin(), message.end(), std::ostream_iterator<int>(oss, " "));
    }
    logger_->debug("Send channel {} conn {} msgBytes {}", int(chID),toString(),oss.str());
    packetMsg pac(chID, message);
    std::stringstream ss;
    pac.Serialize(ss);

    std::string s;
    s+=MsgPacket;
    s+=ss.str();
    sendMonitor_->Update(s.length());
    conn_->send(s.data(), s.length());
}


void Session::onMessage(const muduo::net::TcpConnectionPtr& conn,
                 muduo::net::Buffer* buf,
                 muduo::Timestamp ti)
{
    lastReceived_=ti;
    std::string msg = buf->retrieveAllAsString();
    recvMonitor_->Update(msg.length());

    unsigned char type=msg[0];
    if(type==PingPacket)
    {
        logger_->debug("Receive Ping");
        logger_->debug("Send Pong to {}", conn_->peerAddress().toIpPort());
        std::string s;
        s+=PongPacket;
        conn_->send(s.data(),s.length());
        sendMonitor_->Update(s.length());
    }
    else if(type==PongPacket)
    {
        logger_->debug("Receive Pong");
        loop_->cancel(pongTimeout_);
    }
    else if(type == MsgPacket)
    {
        
    }
}